/* Sentinel - IRC Statistical and Operator Services
** adminserv.c - Administrative service functionality
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: adminserv.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "match.h"
#include "flags.h"
#include "s_string.h"
#include "slog.h"
#include <fcntl.h>

#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

static char errstr[BUFSIZE];

static int a_login(struct User *, int, char **);
static int a_challenge(struct User *, int, char **);
static int a_shutdown(struct User *, int, char **);
static int a_restart(struct User *, int, char **);
static int a_quote(struct User *, int, char **);
static int a_cping(struct User *, int, char **);
static int a_cversion(struct User *, int, char **);
static int a_help(struct User *, int, char **);
static int a_rehash(struct User *, int, char **);
static int a_dcc(struct User *, int, char **);
static int a_chat(struct User *, int, char **);
static int a_logout(struct User *, int, char **);

static struct MsgTable admin_table[] =
  {
    { "HELP", 0, 0, NULL, a_help
    },
    { "LOGIN", 0, 0, NULL, a_login },
    { "CHALLENGE", 0, 0, NULL, a_challenge },
    { "\001DCC", 0, 0, NULL, a_dcc },
    { "CHAT", 0, 0, NULL, a_chat },
    { "DIE", 0, 0, NULL, a_shutdown },
    { "SHUTDOWN", 0, 0, NULL, a_shutdown },
    { "RESTART", 0, 0, NULL, a_restart },
    { "QUOTE", 0, 0, NULL, a_quote },
    { "RAW", 0, 0, NULL, a_quote },
    { "REHASH", 0, 0, NULL, a_rehash },
    { "\001PING", 0, 0, NULL, a_cping },
    { "\001VERSION\001", 0, 0, NULL, a_cversion },
    { "LOGOUT", 0, 0, NULL, a_logout },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "LOGIN", "help/adminserv/login", 0
    },
    { "CHALLENGE", "help/adminserv/challenge", 0 },
    { "SHUTDOWN", "help/adminserv/shutdown", 1 },
    { "RESTART", "help/adminserv/restart", 1 },
    { "REHASH", "help/adminserv/rehash", 1 },
    { "QUOTE", "help/adminserv/quote", 1 },
    { "RAW", "help/adminserv/quote", 1 },
    { "CHAT", "help/adminserv/chat", 1 },
    { "LOGOUT", "help/adminserv/logout", 1 },
    { (char *)0, (char *)0, 0 }
  };

void adminserv(struct User *u, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (!parc)
    return ;

  /* We can't show login credentials, so no logging */

  if (IsAOnlyOpers() && !IsOper(u))
  {
    if (parv[0][0] != '\001')
    {
      notice(u->nick, AdminServ.nick,
              "This service is only available to IRCops.");
      return ;
    }
  }

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; admin_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], admin_table[i].name))
    {
      status = admin_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with adminserv command %s: %s",
             parv[0], (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  notice(u->nick, AdminServ.nick, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int a_help(struct User *u, int parc, char *parv[])
{
  int i;

  spy("%s:  HELP Request by %s for %s", AdminServ.nick, u->nick,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, "%s:  HELP Request by %s for %s",
                  AdminServ.nick, u->nick, (parv[1]) ? parv[1] : "NO QUERY");
  if (parc < 2)
  {
    send_help_file(u->nick, AdminServ.nick, "help/adminserv/index", "index");
    if (u->myuser != NULL)
      send_help_file(u->nick, AdminServ.nick,
                     "help/adminserv/aindex", "aindex");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (u->myuser)
          send_help_file(u->nick, AdminServ.nick, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, AdminServ.nick, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, AdminServ.nick, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, AdminServ.nick, "Unknown Help Topic: %s", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int a_login(struct User *u, int parc, char *parv[])
{
  struct MyUser *myu;
#if defined(CRYPT)

  char * crpass;
#endif

  if (parc < 3)
  {
    notice(u->nick, AdminServ.nick,
            "Syntax: /msg %s LOGIN \37username\37 \37password\37",
            AdminServ.nick);
    strcpy(errstr, "Missing parameters in LOGIN");
    return 1;
  }

  myu = findmyuser(parv[1]);
  if (!myu)
  {
    alert("%s: Failed login attempt (unknown user: %s)",
          AdminServ.nick, parv[1]);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Failed login attempt (unknown user: %s)",
                    AdminServ.nick, parv[1]);
    notice(u->nick, AdminServ.nick, "Unknown User.");
    slog(DEFAULT_LOG, L_ERR, "Failed login attempt (unknown user (%s))",
         parv[1]);
    /* This isn't an error in the code, only to the user */
    return 0;
  }
#if defined(CRYPT)
  crpass = crypt(parv[2], myu->password);
  if (!irccasecmp(myu->username, parv[1]) &&
      !strcmp(myu->password, crpass))
#else

  if (!irccasecmp(myu->username, parv[1]) &&
      !strcmp(myu->password, parv[2]))
#endif /* CRYPT */

  {
    slog(DEFAULT_LOG, L_NOTICE, "%s [%s](%s@%s) sucessfully logged in.",
         u->nick, myu->username, u->username, u->hostname);
    u->myuser = myu;
    alert("%s: Sucessful login by %s to myunick %s", AdminServ.nick,
          u->nick, myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Sucessful login by %s to myunick %s",
                    AdminServ.nick, u->nick, myu->username);
    notice(u->nick, AdminServ.nick, "Permission Granted.");
    notice(u->nick, AdminServ.nick, "Your permissions are:%s",
           show_oper_flags(myu));
    myu->flags |= MFLAG_ISON;
    /* We keep the CURRENT person who LOGIN'ed through that
    ** access entry pointed to by the u pointer.  This is used
    ** to obtain information on the user who logged in, ie.
    ** in STATS p
    */
    myu->u = u;
    return 0;
  }
  else
  {
    slog(DEFAULT_LOG, L_ERR, "%s [%s](%s@%s) failed login.", u->nick,
         myu->username, u->username, u->hostname);
    alert("%s: Failed login attempt (invalid password) by %s on "
          "myuser nick %s", AdminServ.nick, u->nick,
          myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Failed login attempt (invalid password) by %s on "
                    "myuser nick %s", AdminServ.nick, u->nick,
                    myu->username);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    /* This isn't a code error either */
    return 0;
  }

  /* NOT REACHED */
  return 0;
}

static int a_challenge(struct User *u, int parc, char *parv[])
{
#ifdef HAVE_LIBCRYPTO
  struct MyUser *myu;
  char *challenge;

  if (parc < 2)
  {
    notice(u->nick, AdminServ.nick,
            "Syntax: /msg %s CHALLENGE <nick> [response]",
            AdminServ.nick);
    strcpy(errstr, "Missing parameters in CHALLENGE");
    return 1;
  }

  myu = findmyuser(parv[1]);
  if (myu == NULL)
  {
    alert("%s: Failed challenge attempt (unknown user: %s)",
          AdminServ.nick, parv[1]);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Failed challenge attempt (unknown user: %s)",
                    AdminServ.nick, parv[1]);
    notice(u->nick, AdminServ.nick, "Unknown User.");
    slog(DEFAULT_LOG, L_ERR, "Failed challenge attempt (unknown user (%s))",
         parv[1]);
    /* This isn't an error in the code, only to the user */
    return 0;
  }
  /* parc == 2, create the challenge and store the response */
  if (myu->keyfile == NULL)
  {
    notice(u->nick, AdminServ.nick,
            "Challenge is not enabled for your operator block");
    return 0;
  }
  if (parc == 2)
  {
    if (generate_challenge(&challenge, &(myu->response),
                           myu->keyfile) == 0)
    {
      notice(u->nick, AdminServ.nick, "Challenge: %s", challenge);
      return 0;
    }
    else
    {
      notice(u->nick, AdminServ.nick, "Challenge error");
      return 0;
    }
  }
  if ((myu->response != NULL) && (strcmp(parv[2], myu->response) == 0))
  {
    free(myu->response);
    myu->response = NULL;
    slog(DEFAULT_LOG, L_NOTICE, "%s [%s](%s@%s) sucessfully logged in.",
         u->nick, myu->username, u->username, u->hostname);
    u->myuser = myu;
    alert("%s: Sucessful challenge by %s to myunick %s",
          AdminServ.nick, u->nick, myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Sucessful challenge by %s to myunick %s",
                    AdminServ.nick, u->nick, myu->username);
    notice(u->nick, AdminServ.nick, "Permission Granted.");
    notice(u->nick, AdminServ.nick, "Your permissions are:%s",
           show_oper_flags(myu));
    myu->flags |= MFLAG_ISON;
    /* We keep the CURRENT person who LOGIN'ed through that
    ** access entry pointed to by the u pointer.  This is used
    ** to obtain information on the user who logged in, ie.
    ** in STATS p
    */
    myu->u = u;
    return 0;
  }
  else
  {
    slog(DEFAULT_LOG, L_ERR, "%s [%s](%s@%s) failed challenge.", u->nick,
         myu->username, u->username, u->hostname);
    alert("%s: Failed challenge attempt (incorrect response) by %s "
          "on myuser nick %s", AdminServ.nick, u->nick,
          myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "%s: Failed login attempt (incorrect response) by %s "
                    "on myuser nick %s", AdminServ.nick, u->nick,
                    myu->username);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    /* This isn't a code error either */
    return 0;
  }

  /* NOT REACHED */
#else
  notice(u->nick, AdminServ.nick, "Challenge disabled.");
#endif

  return 0;
}

static int a_shutdown(struct User *u, int parc, char *parv[])
{
  char comm[BUFSIZE];

  if (u->myuser == NULL || !IsSetOperAdmin(u->myuser))
  {
    slog(DEFAULT_LOG, L_ERR, "Access Denied (SHUTDOWN) to %s", u->nick);
    alert("%s: SHUTDOWN attempt by %s (failed)", AdminServ.nick,
          u->nick);
    sendto_dcc_flag(CF_NOTICES,
                    "%s: SHUTDOWN attempt by %s (failed)", AdminServ.nick,
                    u->nick);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    /* Not a code error */
    return 0;
  }

  comm[0] = '\0';

  ExpandParv(comm, BUFSIZE, 1, parc, parv);

  /* Yes, we are very nasty like this. */
  wallops(settings.name, "%s requested SHUTDOWN: %s", u->nick,
          (comm[0]) ? comm : "No Reason");
  alert("Service Shutdown by %s: %s", u->nick,
        (comm[0]) ? comm : "No Reason");
  sendto_dcc_flag(CF_NOTICES, "Service Shutdown by %s: %s", u->nick,
                  (comm[0]) ? comm : "No Reason");
  SendQuit(u, 0, comm);
  sleep(1);
  shutdown(servsock, SHUT_RDWR);
  close(servsock);
  close_dccs();
  SaveStats();
  slog(DEFAULT_LOG, L_CRIT, "%s [%s](%s) requested SHUTDOWN: %s", u->nick,
       u->myuser->username, u->hostname, (comm) ? comm : "No Reason");
  close_logfiles();
  exit(0);

  /* NOT REACHED */
  return 0;
}

static int a_restart(struct User *u, int parc, char *parv[])
{
  char comm[BUFSIZE];

  if (u->myuser == NULL || !IsSetOperAdmin(u->myuser))
  {
    slog(DEFAULT_LOG, L_ERR, "Access Denied (RESTART) to %s", u->nick);
    alert("%s: RESTART Attempt by %s (failed)", AdminServ.nick,
          u->nick);
    sendto_dcc_flag(CF_NOTICES,
                    "%s: RESTART Attempt by %s (failed)", AdminServ.nick,
                    u->nick);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    /* Not a code error */
    return 0;
  }

  comm[0] = '\0';

  ExpandParv(comm, BUFSIZE, 1, parc, parv);

  /* Yes, we are very nasty like this. */
  wallops(settings.name, "%s requested RESTART: %s", u->nick,
          (comm[0]) ? comm : "No Reason");
  alert("Service Restart by %s: %s", u->nick,
        (comm[0]) ? comm : "No Reason");
  sendto_dcc_flag(CF_NOTICES, "Service Restart by %s: %s", u->nick,
                  (comm[0]) ? comm : "No Reason");
  SendQuit(u, 1, comm);
  sleep(1);
  shutdown(servsock, SHUT_RDWR);
  close(servsock);
  close_dccs();
  SaveStats();
  slog(DEFAULT_LOG, L_CRIT, "%s [%s](%s) requested RESTART: %s", u->nick,
       u->myuser->username, u->hostname, (comm) ? comm : "No Reason");
  execv(SPATH, NULL);

  /* NOT REACHED */
  return 0;
}

static int a_quote(struct User *u, int parc, char *parv[])
{
#ifdef ENABLE_QUOTE
  char command[BUFSIZE];

  if (u->myuser == NULL || !IsSetOperAdmin(u->myuser))
  {
    slog(DEFAULT_LOG, L_ERR, "Access Denied (QUOTE) to %s", u->nick);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    /* Not an error in the code */
    return 0;
  }

  ExpandParv(command, BUFSIZE, 1, parc, parv);

  if (command[0] == '\0')
  {
    notice(u->nick, AdminServ.nick, "Please enter a valid command");
    strcpy(errstr, "NULL command attempt in QUOTE");
    return 1;
  }

  sts("%s", command);
  slog(DEFAULT_LOG, L_WARNING, "%s!%s@%s made me execute %s", u->nick,
       u->username, u->hostname, command);
#else

  notice(u->nick, AdminServ.nick, "QUOTE/RAW Disabled.");
#endif

  return 0;
}

static int a_cping(struct User *u, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);

  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", AdminServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int a_cversion(struct User *u, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001",
      AdminServ.nick, u->nick, stats_version, VersionFlags(), stats_date);
  slog(DEFAULT_LOG, L_INFO, "CTCP VERSION from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int a_rehash(struct User *u, int parc, char *parv[])
{
  if (u->myuser == NULL || !IsSetOperAdmin(u->myuser))
  {
    slog(DEFAULT_LOG, L_ERR, "Access Denied (REHASH) to %s", u->nick);
    notice(u->nick, AdminServ.nick, "Access Denied.");
    alert("%s: REHASH Attempt by %s (failed)", AdminServ.nick,
          u->nick);
    sendto_dcc_flag(CF_NOTICES, "%s: REHASH Attempt by %s (failed)",
                    AdminServ.nick, u->nick);
    /* Not a code error */
    return 0;
  }

  rehash();

  alert("%s: REHASH by %s", AdminServ.nick, u->nick);
  sendto_dcc_flag(CF_NOTICES, "%s: REHASH by %s", AdminServ.nick,
                  u->nick);
  notice(u->nick, AdminServ.nick, "REHASH complete");

  return 0;
}

static int a_dcc(struct User *u, int parc, char *parv[])
{
  if (parc < 2)
  {
    strcpy(errstr, "Not enough parameters to DCC");
    return 1;
  }

  if (IsDCCOperOnly())
  {
    if (!IsOper(u))
    {
      notice(u->nick, AdminServ.nick, "DCC is restricted to opers only.");
      alert("DCC attempt by non-oper %s", u->nick);
      sendto_dcc_flag(CF_AUTH, "DCC attempt by non-oper %s",
                      u->nick);
      return 0;
    }
  }

  if (!strcasecmp(parv[1], "CHAT"))
  {
    int sock;
    struct sockaddr_in sa;
    unsigned long hl;
    int optval;
    int flags;
    int port;

    /* \001DCC CHAT chat longip port\001 */
    if (parc < 5)
    {
      strcpy(errstr, "Not enough parameters to DCC CHAT");
      return 1;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      strcpy(errstr, "Unable to create socket");
      return 1;
    }

    if (sock > me.max_fd)
      me.max_fd = sock;

    optval = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&optval,
               sizeof(optval));

    /* XXX vhost */
    memset((void *) &sa, '\0', sizeof(sa));
    sa.sin_family = AF_INET;
    port = atoi(parv[4]);
    if ((port < 1024) || (port > 65535))
    {
      notice(u->nick, AdminServ.nick, "DCC port invalid.");
      snprintf(errstr, BUFSIZE, "DCC Port %d Invalid.", port);
      return 1;
    }
    sa.sin_port = htons(port);
    hl = strtoul(parv[3], NULL, 10);
    sa.sin_addr.s_addr = htonl(hl);

    /* set non blocking, the POSIX way */
    flags = fcntl(sock, F_GETFL, 0);
    flags |= O_NONBLOCK;
    (void) fcntl(sock, F_SETFL, flags);

    connect(sock, (struct sockaddr *) &sa, sizeof(sa));
    add_connection(sock, u->nick, CONN_DCCOUT | CONN_CONNECTING);
    return 0;
  }

  strcpy(errstr, "Unknown DCC option");
  return 1;
}

static int a_chat(struct User *u, int parc, char *parv[])
{
  int sock;
  struct sockaddr_in sa;
  int optval;
  int flags;
  int dcc_port;
  int res = -1;
  struct hostent *local_host;
  unsigned long l_ip;

  if (IsDCCOperOnly())
  {
    if (!IsOper(u))
    {
      notice(u->nick, AdminServ.nick, "DCC is restricted to opers only.");
      alert("DCC attempt by non-oper %s", u->nick);
      sendto_dcc_flag(CF_AUTH, "DCC attempt by non-oper %s",
                      u->nick);
      return 0;
    }
  }

  if (me.local_host == NULL)
  {
    notice(u->nick, AdminServ.nick, "DCC Failed.");
    slog(DEFAULT_LOG, L_NOTICE, "DCC Failed, local ip unknown.  Try setting "
         "localip in the conf file.");
    return 0;
  }

  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    strcpy(errstr, "Unable to create socket");
    return 1;
  }

  if (sock > me.max_fd)
    me.max_fd = sock;

  optval = 1;
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&optval,
             sizeof(optval));

  for (dcc_port = settings.dcc_low_port;
       dcc_port < settings.dcc_high_port; dcc_port++)
  {
    memset((void *) &sa, '\0', sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = INADDR_ANY;
    sa.sin_port = htons(dcc_port);
    if ((res = bind(sock, (struct sockaddr *) & sa,
                    sizeof(sa)) >= 0))
    {
      break;
    }
  }

  if (res < 0)
  {
    close(sock);
    notice(u->nick, AdminServ.nick, "Unable to DCC CHAT");
    strcpy(errstr, "Unable to bind DCC port");
    return 1;
  }

  /* set non blocking, the POSIX way */
  flags = fcntl(sock, F_GETFL, 0);
  flags |= O_NONBLOCK;
  (void) fcntl(sock, F_SETFL, flags);

  /* Only one client will DCC in to the port given below, so the
  ** listen backlog should just be 1
  */
  if (listen(sock, 1) < 0)
  {
    close(sock);
    notice(u->nick, AdminServ.nick, "Unable to DCC CHAT");
    strcpy(errstr, "Unable to listen()");
    return 1;
  }

  /* accept() will be called when it triggers the readfds in select() */
  add_connection(sock, u->nick, CONN_DCCIN | CONN_CONNECTING);
  /* XXX - vhost here */
  /* XXX - optimize as well */
  local_host = me.local_host;
  (void) memcpy((void *) &l_ip, (void *) local_host->h_addr,
                sizeof(local_host->h_addr));
  l_ip = htonl(l_ip);

  sts(":%s PRIVMSG %s :\001DCC CHAT chat %lu %d\001", AdminServ.nick,
      u->nick, l_ip, dcc_port);

  return 0;
}

static int a_logout(struct User *u, int parc, char *parv[])
{
  dlink_node *dl;

  if (u->myuser == NULL)
  {
    notice(u->nick, AdminServ.nick, "Not logged in.");
    return 0;
  }

  u->myuser->flags &= ~MFLAG_ISON;
  if (IsOperDying(u->myuser) && !IsOperDCC(u->myuser))
  {
    dl = dlink_find_delete(u->myuser, &myudying);
    dlink_free(dl);
#ifdef HAVE_LIBCRYPTO

    if (u->myuser->response != NULL)
      free(u->myuser->response);
    if (u->myuser->keyfile != NULL)
      RSA_free(u->myuser->keyfile);
#endif

    free(u->myuser);
    memcounts.myuser--;
  }
  u->myuser = NULL;
  notice(u->nick, AdminServ.nick, "Logged out.");
  return 0;
}

